package com.nageoffer.jobtrain.judge.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.nageoffer.jobtrain.judge.common.constant.UserJudgeConstant;
import com.nageoffer.jobtrain.judge.common.enums.ExecuteJudeCodeEnums;
import com.nageoffer.jobtrain.judge.common.enums.UserPassOjQuestionEnums;
import com.nageoffer.jobtrain.judge.common.result.SandBoxExecuteResult;
import com.nageoffer.jobtrain.judge.dao.entity.ContestAnswerDO;
import com.nageoffer.jobtrain.judge.dao.entity.UserAnswerDO;
import com.nageoffer.jobtrain.judge.dao.mapper.ContestAnswerMapper;
import com.nageoffer.jobtrain.judge.dao.mapper.UserAnswerMapper;
import com.nageoffer.jobtrain.judge.dto.resp.OjQuestionSubmitRespDTO;
import com.nageoffer.jobtrain.judge.service.OjQuestionJudgeService;
import com.nageoffer.jobtrain.judge.service.SandBoxPoolService;
import com.nageoffer.jobtrain.judge.service.SandBoxService;
import com.nageoffer.jobtrain.judge.service.dto.OjQuestionSubmitCaseDTO;
import com.nageoffer.jobtrain.judge.service.dto.OjQuestionSubmitJudgeDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 系统判题接口实现层
 * author：唐晨
 */
@Service
@RequiredArgsConstructor
public class OjQuestionJudgeServiceImpl implements OjQuestionJudgeService {
    private final SandBoxService sandBoxService;
    private final UserAnswerMapper userAnswerMapper;
    private final ContestAnswerMapper contestAnswerMapper;
    private final SandBoxPoolService sandBoxPoolService;

    @Override
    public OjQuestionSubmitRespDTO ojQuestionJudge(OjQuestionSubmitJudgeDTO requestParam) {
        // 通过代码沙箱进行执行代码，拿到执行的返回结果
        SandBoxExecuteResult sandBoxExecuteResult = sandBoxPoolService.executeJudeCode(requestParam.getPhoneNumber(),
                requestParam.getTotalCode(), requestParam.getInputList());

        // SandBoxExecuteResult sandBoxExecuteResult = sandBoxService.executeJudeCode(requestParam.getPhoneNumber(),
        //                requestParam.getTotalCode(), requestParam.getInputList());

        // 实际要返回的结果
        OjQuestionSubmitRespDTO ojQuestionSubmitRespDTO = new OjQuestionSubmitRespDTO();
        if (sandBoxExecuteResult != null && ObjectUtil.equal(sandBoxExecuteResult.getExecuteJudeCodeEnums(), ExecuteJudeCodeEnums.SUCCEED)) {
            // 代表系统执行代码成功 -先比对测试用例，再进行时间、空间限制的对比判题
            ojQuestionSubmitRespDTO = handlerJudgeCode(requestParam, sandBoxExecuteResult, ojQuestionSubmitRespDTO);
        } else {
            // 代表系统执行判题逻辑异常，根据异常描述进行判断
            ojQuestionSubmitRespDTO.setPass(UserPassOjQuestionEnums.FAILED.getType());
            if (sandBoxExecuteResult != null) {
                ojQuestionSubmitRespDTO.setExecuteMessage(sandBoxExecuteResult.getErrorMessage());
            } else {
                ojQuestionSubmitRespDTO.setExecuteMessage(ExecuteJudeCodeEnums.UNKNOWN_FAILED.getDescribe());
            }
            ojQuestionSubmitRespDTO.setScore(UserJudgeConstant.ERROR_SCORE);
        }

        // 将用户提交的代码执行结果等信息保存到数据库中
        handlerSaveUserSubmitCode(requestParam, ojQuestionSubmitRespDTO);
        return ojQuestionSubmitRespDTO;
    }

    private OjQuestionSubmitRespDTO handlerJudgeCode(OjQuestionSubmitJudgeDTO requestParam,
                                                     SandBoxExecuteResult sandBoxExecuteResult,
                                                     OjQuestionSubmitRespDTO ojQuestionSubmitRespDTO) {
        // 这个列表代表实际将测试输入用例，交给判题逻辑，进行输出的结果列表
        List<String> executeOutPutList = sandBoxExecuteResult.getExecuteOutPutList();
        // 这个列表代表数据库中原始的测试用例输出列表
        List<String> outputList = requestParam.getOutputList();
        if (executeOutPutList.size() != outputList.size()) {
            // 设置响应返回结果，当数量执行完输出测试用例之后
            ojQuestionSubmitRespDTO.setPercent(0);
            ojQuestionSubmitRespDTO.setPass(UserPassOjQuestionEnums.FAILED.getType());
            ojQuestionSubmitRespDTO.setScore(UserJudgeConstant.ERROR_SCORE);
            ojQuestionSubmitRespDTO.setExecuteMessage(ExecuteJudeCodeEnums.NOT_ALL_PASSED.getDescribe());
            return ojQuestionSubmitRespDTO;
        }
        // 存储实际的判题结果，输入、输出、实际输出
        List<OjQuestionSubmitCaseDTO> actualPassTestCaseList = new ArrayList<>();
        AtomicInteger passCount = new AtomicInteger(0); // 表示通过的测试用例个数
        boolean flag = handlerMathTestCase(executeOutPutList, outputList, requestParam.getInputList(), actualPassTestCaseList, passCount);

        // 取出匹配通过的测试用例值 - 进行设置
        int finalPassCount = passCount.get();
        ojQuestionSubmitRespDTO.setActualPassTestCaseList(actualPassTestCaseList);
        // 计算通过的百分比并转为整数
        int percent = (int) Math.round((double) finalPassCount / requestParam.getOutputList().size() * 100);
        // 设置百分比值
        ojQuestionSubmitRespDTO.setPercent(percent);

        // 进行空间、时间限制的判断
        return handlerJudgeSpaceAndTime(requestParam, sandBoxExecuteResult, ojQuestionSubmitRespDTO, flag);
    }

    private boolean handlerMathTestCase(List<String> executeOutPutList,
                                        List<String> outputList,
                                        List<String> inputList,
                                        List<OjQuestionSubmitCaseDTO> actualPassTestCaseList,
                                        AtomicInteger passCount) {
        // 数量匹配，现在去匹配输出用例是否正确
        boolean flag = true;
        for (int index = 0; index < outputList.size(); index++) {
            // 获取输入，输出，实际输出测试用例
            String input = inputList.get(index);
            String outPut = outputList.get(index);
            String executeOutPut = executeOutPutList.get(index);

            // 将输入，输出，实际输出测试用例，填入到执行结果列表中
            OjQuestionSubmitCaseDTO ojQuestionSubmitCaseDTO = new OjQuestionSubmitCaseDTO();
            ojQuestionSubmitCaseDTO.setInput(input);
            ojQuestionSubmitCaseDTO.setOutPut(outPut);
            ojQuestionSubmitCaseDTO.setActualOutPut(executeOutPut);
            actualPassTestCaseList.add(ojQuestionSubmitCaseDTO);
            if (ObjectUtil.equal(outPut, executeOutPut)) {
                passCount.incrementAndGet();
            } else {
                // 不能进行 break，会导致后面测试用例无法进行写入
                flag = false;
            }
        }
        return flag;
    }

    private OjQuestionSubmitRespDTO handlerJudgeSpaceAndTime(OjQuestionSubmitJudgeDTO requestParam,
                                                             SandBoxExecuteResult sandBoxExecuteResult,
                                                             OjQuestionSubmitRespDTO ojQuestionSubmitRespDTO,
                                                             boolean flag) {
        // 判断是否通过全部测试用例
        if (!flag) {
            ojQuestionSubmitRespDTO.setPass(UserPassOjQuestionEnums.FAILED.getType());
            ojQuestionSubmitRespDTO.setScore(UserJudgeConstant.ERROR_SCORE);
            ojQuestionSubmitRespDTO.setExecuteMessage(ExecuteJudeCodeEnums.NOT_ALL_PASSED.getDescribe());
            return ojQuestionSubmitRespDTO;
        }

        // 走到这里说明通过全部测试用例，进行空间以及时间限制对比
        if (sandBoxExecuteResult.getUseMemory() > requestParam.getSpaceLimit()) {
            ojQuestionSubmitRespDTO.setPass(UserPassOjQuestionEnums.FAILED.getType());
            ojQuestionSubmitRespDTO.setScore(UserJudgeConstant.ERROR_SCORE);
            ojQuestionSubmitRespDTO.setExecuteMessage(ExecuteJudeCodeEnums.OUT_OF_MEMORY.getDescribe());
            return ojQuestionSubmitRespDTO;
        }
        if (sandBoxExecuteResult.getUseTime() > requestParam.getSpaceLimit()) {
            ojQuestionSubmitRespDTO.setPass(UserPassOjQuestionEnums.FAILED.getType());
            ojQuestionSubmitRespDTO.setScore(UserJudgeConstant.ERROR_SCORE);
            ojQuestionSubmitRespDTO.setExecuteMessage(ExecuteJudeCodeEnums.OUT_OF_TIME.getDescribe());
            return ojQuestionSubmitRespDTO;
        }

        // 走到这里说明，通过所有测试用例以及时间限制、空间限制
        ojQuestionSubmitRespDTO.setPass(UserPassOjQuestionEnums.PASSING.getType());
        ojQuestionSubmitRespDTO.setScore(requestParam.getDifficulty() * UserJudgeConstant.SUCCESS_DEFAULT_SCORE);
        return ojQuestionSubmitRespDTO;
    }

    /**
     * 进行将用户答题结果存储到数据库中
     */
    private void handlerSaveUserSubmitCode(OjQuestionSubmitJudgeDTO requestParam, OjQuestionSubmitRespDTO ojQuestionSubmitRespDTO) {
        // 判断是竞赛判题还是精选OJ判题
        if (ObjectUtil.isNotNull(requestParam.getContestId())) {
            // 此时说明是竞赛判题逻辑
            ContestAnswerDO contestAnswerDO = ContestAnswerDO.builder()
                    .questionId(requestParam.getQuestionId())
                    .pass(ojQuestionSubmitRespDTO.getPass())
                    .contestId(requestParam.getContestId())
                    .submitCode(requestParam.getUserSubmitCode())
                    .totalCode(requestParam.getTotalCode())
                    .codeType(requestParam.getCodeType())
                    .percent(ojQuestionSubmitRespDTO.getPercent())
                    .scores(ojQuestionSubmitRespDTO.getScore())
                    .executeResult(ojQuestionSubmitRespDTO.getExecuteMessage())
                    .executeOutputCaseResult(JSON.toJSONString(ojQuestionSubmitRespDTO.getActualPassTestCaseList()))
                    .phoneNumber(requestParam.getPhoneNumber())
                    .build();
            // 先删除之前的再进行新增，保留最后一次结果
            LambdaQueryWrapper<ContestAnswerDO> queryWrapper = Wrappers.lambdaQuery(ContestAnswerDO.class)
                    .eq(ContestAnswerDO::getQuestionId, requestParam.getQuestionId())
                    .eq(ContestAnswerDO::getContestId, requestParam.getContestId())
                    .eq(ContestAnswerDO::getPhoneNumber, requestParam.getPhoneNumber());
            contestAnswerMapper.delete(queryWrapper);
            contestAnswerMapper.insert(contestAnswerDO);
        } else {
            UserAnswerDO userAnswerDO = UserAnswerDO.builder()
                    .questionId(requestParam.getQuestionId())
                    .pass(ojQuestionSubmitRespDTO.getPass())
                    .submitCode(requestParam.getUserSubmitCode())
                    .totalCode(requestParam.getTotalCode())
                    .codeType(requestParam.getCodeType())
                    .percent(ojQuestionSubmitRespDTO.getPercent())
                    .executeResult(ojQuestionSubmitRespDTO.getExecuteMessage())
                    .executeOutputCaseResult(JSON.toJSONString(ojQuestionSubmitRespDTO.getActualPassTestCaseList()))
                    .phoneNumber(requestParam.getPhoneNumber())
                    .build();
            LambdaQueryWrapper<UserAnswerDO> queryWrapper = Wrappers.lambdaQuery(UserAnswerDO.class)
                    .eq(UserAnswerDO::getQuestionId, requestParam.getQuestionId())
                    .eq(UserAnswerDO::getPhoneNumber, requestParam.getPhoneNumber());
            userAnswerMapper.delete(queryWrapper);
            userAnswerMapper.insert(userAnswerDO);
        }
    }
}
